--[[

ether_lands is a Minetest mod that adds its own floatlands-like map generation
Copyright (C) 2019 Kurtzmusch

This file is part of ether_lands

ether_lands is free software; you can redistribute it and/or modify it under 
the terms of the GNU Lesser General Public License as published by the Free 
Software Foundation; either version 2.1 of the License, or (at your option) any
later version.

ether_lands is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with ether_lands. If not, see <https://www.gnu.org/licenses/>.

--]]

-- based on paramat's mapgen: fracture

-- global tables --------------------------------------------------------------
 ether_lands = {} --external exposition of mod's tables and functions
_ether_lands = {} --internal ^

-- terrain parameters ---------------------------------------------------------
local REALM_START = 2000 -- initial height for this realm
local STONE_THRESHOLD = 0.03
local DIRT_THRESHOLD = 0
local HEIGHT_VARIANCE = 800 -- total height variance for the heightmap used to calculate biomes and overall height of layers

local GRASS_FREQUENCY = 0.7
local TREE_FREQUENCY = 0.001
local TREE3_FREQUENCY = 0.1

local CYANLANDS_DISPLACEMENT = 20
local CYANLANDS_LAYER_HEIGHT = 350
local CYANLANDS_BOTTOMGRADIENT_LENGHT = 32
local CYANLANDS_TOPGRADIENT_LENGTH = 250 -- used to ease of the density of the noise as height within the layer increases
local CYANLANDS_TOPGRADIENT_START = CYANLANDS_LAYER_HEIGHT-CYANLANDS_TOPGRADIENT_LENGTH -- height within the layer for the top gradient
local CYANLANDS_HORIZONTAL_UPPERGRADIENT_LENGTH = 40 -- used to ease of the density of the noise in the edge with bluelands
local CYANLANDS_HORIZONTAL_LOWERGRADIENT_LENGTH = 40 -- ^ orangelands

-- TODO _BIOME_HEIGHT should have the same meaning for any biome
local BLUELANDS_DISPLACEMENT = 20
local BLUELANDS_BIOME_HEIGHT = HEIGHT_VARIANCE - 200 -- when the heightmap is above this, the biome is bluelands
local BLUELANDS_LAYER_HEIGHT = 200
local BLUELANDS_BOTTOMGRADIENT_LENGHT = 32
local BLUELANDS_TOPGRADIENT_LENGTH = 100
local BLUELANDS_TOPGRADIENT_START = BLUELANDS_LAYER_HEIGHT-BLUELANDS_TOPGRADIENT_LENGTH
local BLUELANDS_HORIZONTAL_LOWERGRADIENT_LENGTH = 50

local ORANGELANDS_LAYER_HEIGHT = 200
local ORANGELANDS_TOPGRADIENT_LENGTH = 100
local ORANGELANDS_BOTTOMGRADIENT_LENGHT = 32
local ORANGELANDS_TOPGRADIENT_START = ORANGELANDS_LAYER_HEIGHT-ORANGELANDS_TOPGRADIENT_LENGTH
local ORANGELANDS_BIOME_HEIGHT = 250 -- when the heightmap is below this, the biome is orangelands
local ORANGELANDS_HORIZONTAL_UPPERGRADIENT_LENGTH = 50

local REALM_FINISH = REALM_START+HEIGHT_VARIANCE
	+CYANLANDS_LAYER_HEIGHT+ORANGELANDS_LAYER_HEIGHT+BLUELANDS_LAYER_HEIGHT
	+CYANLANDS_DISPLACEMENT+BLUELANDS_DISPLACEMENT

-- debug stuff
local DEBUG_FLAGS = { debug_blocks=false, overwrite_seed = false, cherrypicked_spawn = false, cherrypick_biome=false, give_privs=false }
local DEBUG_FREQUENCY = 5
local DESIRED_BIOME = "orangelands"
local debug_data = {
	mapblock_count = 0,
	timeAccumulator_total = 0,
	timeAccumulator_getNoise = 0,
	timeAccumulator_genIDs = 0,
	timeAccumulator_writeVM = 0,
	timeAccumulator_writeMap = 0,
	timeAccumulator_calcLight = 0,
	timeAccumulator_placeSchematics = 0,
}

local schematics_path = minetest.get_modpath("ether_lands").."/schematics/"

ether_lands.schematics = {
	path = minetest.get_modpath("ether_lands").."/schematics/",
	cyanlands = {
		tree1 = schematics_path.."cyanlands_tree1.mts",
		tree2 = schematics_path.."cyanlands_tree2.mts",
		tree3 = schematics_path.."cyanlands_tree3.mts",
	},
	orangelands = {
		tree1 = schematics_path.."orangelands_tree1.mts",
	},
	bluelands = {
		tree1 = schematics_path.."bluelands_tree1.mts",
	},
}

-- noise parameters -----------------------------------------------------------

ether_lands.trees_parameters = {
	offset = 0,
	scale = 1,
	spread = { x = 1200, y = 1200, z = 1200 },
	seed = 7,
	octaves = 2,
	persist = 1,
	lacunarity = 16,
}

ether_lands.heightmap_parameters = {
	offset = 0,
	scale = 1,
	spread = { x = 1200, y = 1200, z = 1200 },
	seed = 7,
	octaves = 1,
	persist = 1
}

ether_lands.cyanland_noise1_parameters = {
  offset = 0,
  scale = 1,
  spread = {x=384, y=128, z=384},
  seed = 593,
  octaves = 5,
  persist = 0.67
}

ether_lands.cyanland_noise2_parameters = {
	--second noise in golden ratio
  offset = 0,
  scale = 1,
  spread = {x=311, y=104, z=311},
  seed = 593,
  octaves = 5,
  persist = 0.67
}
-- mapgen ---------------------------------------------------------------------
local modpath = minetest.get_modpath("ether_lands")
dofile(modpath .. "/nodes.lua")

local cid_debugnode_mapchunk = minetest.get_content_id("default:meselamp")
local cid_debugnode_edge = minetest.get_content_id("ether_lands:debugnode_edge")
local cid_debugnode_actualedge = minetest.get_content_id("ether_lands:debugnode_actualedge")

local cid_stone_cyan = minetest.get_content_id("ether_lands:levistone_cyan")
local cid_stone_blue = minetest.get_content_id("ether_lands:levistone_blue")
local cid_stone_orange = minetest.get_content_id("ether_lands:levistone_orange")

local cid_grass_cyan = minetest.get_content_id("ether_lands:grass_cyan")
local cid_grass_blue = minetest.get_content_id("ether_lands:grass_blue")
local cid_grass_orange = minetest.get_content_id("ether_lands:grass_orange")

local cid_dirt_cyan = minetest.get_content_id("ether_lands:dirt_cyan")
local cid_dirt_blue = minetest.get_content_id("ether_lands:dirt_blue")
local cid_dirt_orange = minetest.get_content_id("ether_lands:dirt_orange")

local cid_grass1_cyan = minetest.get_content_id("ether_lands:plant_1_grass_cyan")
local cid_grass1_blue = minetest.get_content_id("ether_lands:plant_1_grass_blue")
local cid_grass1_orange = minetest.get_content_id("ether_lands:plant_1_grass_orange")

local cid_air = minetest.get_content_id("air")

local emerged_dimensions
local mapchunk_dimensions
local cid_data
local parama2_data
local cyanland1_values
local cyanland2_values
local heightmap_values
local mapchunk_length -- assumes mapchunks and emerged vmanips are always cubes
local emerged_length

minetest.register_on_mapgen_init( function(mapgen_params)
	if( DEBUG_FLAGS.overwrite_seed ) then minetest.set_mapgen_setting("seed", 10100000, true) end
	mapchunk_length = minetest.get_mapgen_setting("chunksize")*16 --TODO 16 should not be hard coded
	emerged_length = mapchunk_length+(16*2) --TODO figure out if emerged areas is always 1 mapblock bigger in all directions
	
	cid_data = {}
	cid_data[emerged_length^3] = cid_air
	param2_data = {}
	param2_data[emerged_length^3] = 0
	-- really hoping these are creating a contiguos array
	cyanland1_values = {}
	cyanland1_values[mapchunk_length^3] = 0
	cyanland2_values = {}
	cyanland2_values[mapchunk_length^3] = 0
	heightmap_values = {}
	heightmap_values[mapchunk_length^2] = 0
	
end )

-- places schematics directly in the map
-- TODO figure out why placing on vmanip isnt working
_ether_lands.generate_structures =
function( cid_data, generate_VoxelArea, buffer_VoxelArea )
	
	local zmin = generate_VoxelArea.MinEdge.z
	local zmax = generate_VoxelArea.MaxEdge.z
	local ymin = generate_VoxelArea.MinEdge.y
	local ymax = generate_VoxelArea.MaxEdge.y
	local xmin = generate_VoxelArea.MinEdge.x
	local xmax = generate_VoxelArea.MaxEdge.x
	local node_index_in_emerged
	local node_index_in_mapchunk
	local node_2d_index = 1

	
	for z = zmin, zmax do
		for y = ymin, ymax do
			for x = xmin, xmax do
				local base_height = heightmap_values[node_2d_index] --TODO cache this for every x,z only
				base_height = (base_height+1)/2
				base_height = base_height * HEIGHT_VARIANCE
				local grass_id
				local trees_schematics
				if ( base_height > ORANGELANDS_BIOME_HEIGHT ) then
					grass_id = cid_grass_cyan
					trees_schematics = ether_lands.schematics.cyanlands
				end
				if( base_height > BLUELANDS_BIOME_HEIGHT ) then
					grass_id = cid_grass_blue
					trees_schematics = ether_lands.schematics.bluelands
				end
				if( base_height < ORANGELANDS_BIOME_HEIGHT ) then
					grass_id = cid_grass_orange
					trees_schematics = ether_lands.schematics.orangelands
				end
				local node_index_in_emerged = buffer_VoxelArea:index(x,y,z)
				if( cid_data[node_index_in_emerged] == grass_id ) then
					if( math.random() < TREE_FREQUENCY ) then
					local index_west = buffer_VoxelArea:index(x-1,y,z)
					local index_east = buffer_VoxelArea:index(x+1,y,z)
					local index_south = buffer_VoxelArea:index(x,y,z-1)
					local index_north = buffer_VoxelArea:index(x,y,z+1)
					if (cid_data[index_west] == grass_id
					and(cid_data[index_east] == grass_id )
					and(cid_data[index_south] == grass_id )
					and(cid_data[index_north] == grass_id )
					) then
						minetest.place_schematic(
							{x=x, y=y, z=z},
							trees_schematics.tree1,
							"random",
							nil,
							true,
							"place_center_x,place_center_z"
						)
					end
					end
				end
			end
		end
	end

end

-- generates an area of the map and feeds the buffers, can be used for the mapgen, finding a spawnpoint or anything
-- that needs to know about the terrain before its been generated
-- the mapgen will call this and give it the voxelmanip buffers
-- noise_buffers must be { heightmap_buffer=buffer, cyanland1_buffer=buffer, cyanland2_buffer=buffer }
-- generate_VoxelArea is the area to be generated,
-- buffer_VoxelArea is the area to be writen too( the sizes will match cid_buffer and param2_buffer), often times it is bigger than the generated area so the indexes are not the same

ether_lands.generate_area =
function( generate_VoxelArea, buffer_VoxelArea, cid_buffer, param2_buffer, noise_buffers, vmanip )

	local time_init
	
	local cyanland_noise1_generator = minetest.get_perlin_map(
	ether_lands.cyanland_noise1_parameters, generate_VoxelArea:getExtent() )
	
	local cyanland_noise2_generator = minetest.get_perlin_map(
	ether_lands.cyanland_noise2_parameters, generate_VoxelArea:getExtent() )
	
	local heightmap_generator = minetest.get_perlin_map(
	ether_lands.heightmap_parameters, {x=generate_VoxelArea:getExtent().x, y=generate_VoxelArea:getExtent().z} )
	

	time_init = os.clock()
	heightmap_generator:get_2d_map_flat(
		{x=generate_VoxelArea.MinEdge.x, y=generate_VoxelArea.MinEdge.z}, noise_buffers.heightmap_buffer )
	-- TODO only retrieve 3d noises if this mapchunk overlaps the biome edges
	-- otherwise skip get_map, and loops (maybe even return error so calc lighting and writing back can be skept)
	cyanland_noise1_generator:get_3d_map_flat(
		generate_VoxelArea.MinEdge, noise_buffers.cyanland1_buffer )
	cyanland_noise2_generator:get_3d_map_flat(
		generate_VoxelArea.MinEdge, noise_buffers.cyanland2_buffer )

	time_elapsed = ( os.clock()-time_init )*1000
	debug_data.timeAccumulator_getNoise = debug_data.timeAccumulator_getNoise + time_elapsed

	time_init = os.clock()
	local zmin = generate_VoxelArea.MinEdge.z
	local zmax = generate_VoxelArea.MaxEdge.z
	local ymin = generate_VoxelArea.MinEdge.y
	local ymax = generate_VoxelArea.MaxEdge.y
	local xmin = generate_VoxelArea.MinEdge.x
	local xmax = generate_VoxelArea.MaxEdge.x
	local node_index_in_emerged
	local node_index_in_mapchunk
	local node_2d_index = 1

	local grass_id = cid_grass_cyan
	local levistone_id = cid_stone_cyan
	local dirt_id = cid_dirt_cyan
	local plant1_id = cid_grass1_cyan

	for z = zmin, zmax do
		for y = ymin, ymax do
			for x = xmin, xmax do
				-- lots of stuff here can happen once per height
				local base_height = noise_buffers.heightmap_buffer[node_2d_index] --TODO cache this for every x,z only
				local height
				local max_height
				local top_gradient_start
				local top_gradient_length
				local horizontal_gradient
				local horizontal_gradient_start
				horizontal_gradient = 1
				node_2d_index = node_2d_index + 1
				base_height = (base_height+1)/2
				base_height = base_height * HEIGHT_VARIANCE
				
				height = base_height
				height = height + REALM_START
				
				
				--TODO precalc most of these and add horizontal_uppergradient_start and __lowergradient_start
				if( base_height < ORANGELANDS_BIOME_HEIGHT ) then
					levistone_id = cid_stone_orange
					grass_id = cid_grass_orange
					dirt_id = cid_dirt_orange
					plant1_id = cid_grass1_orange
					max_height = height+ORANGELANDS_LAYER_HEIGHT
					bottom_gradient_start = height
					bottom_gradient_length = ORANGELANDS_BOTTOMGRADIENT_LENGHT
					bottom_gradient_end = bottom_gradient_start + bottom_gradient_length
					top_gradient_start = height + ORANGELANDS_TOPGRADIENT_START
					top_gradient_length = ORANGELANDS_TOPGRADIENT_LENGTH
					horizontal_gradient_start = ORANGELANDS_BIOME_HEIGHT-ORANGELANDS_HORIZONTAL_UPPERGRADIENT_LENGTH
					horizontal_gradient = 1
					if( base_height > horizontal_gradient_start ) then
						horizontal_gradient = (ORANGELANDS_BIOME_HEIGHT-base_height)/ORANGELANDS_HORIZONTAL_UPPERGRADIENT_LENGTH
					end
				end
				if ( base_height > ORANGELANDS_BIOME_HEIGHT ) then
					height = height + ORANGELANDS_LAYER_HEIGHT+CYANLANDS_DISPLACEMENT
					max_height = height + CYANLANDS_LAYER_HEIGHT
					grass_id = cid_grass_cyan
					levistone_id = cid_stone_cyan
					dirt_id = cid_dirt_cyan
					plant1_id = cid_grass1_cyan
					bottom_gradient_start = height
					bottom_gradient_length = CYANLANDS_BOTTOMGRADIENT_LENGHT
					bottom_gradient_end = bottom_gradient_start + bottom_gradient_length
					top_gradient_start = height + CYANLANDS_TOPGRADIENT_START
					top_gradient_length = CYANLANDS_TOPGRADIENT_LENGTH
					horizontal_gradient = 1
					if( base_height < ORANGELANDS_BIOME_HEIGHT+CYANLANDS_HORIZONTAL_LOWERGRADIENT_LENGTH ) then
							horizontal_gradient_start = ORANGELANDS_BIOME_HEIGHT+CYANLANDS_HORIZONTAL_LOWERGRADIENT_LENGTH
							if( base_height < horizontal_gradient_start ) then
								horizontal_gradient = (base_height-ORANGELANDS_BIOME_HEIGHT)/CYANLANDS_HORIZONTAL_LOWERGRADIENT_LENGTH
							end
					elseif( base_height > BLUELANDS_BIOME_HEIGHT-CYANLANDS_HORIZONTAL_UPPERGRADIENT_LENGTH ) then
						horizontal_gradient_start = BLUELANDS_BIOME_HEIGHT-CYANLANDS_HORIZONTAL_UPPERGRADIENT_LENGTH
						if( base_height > horizontal_gradient_start ) then
							horizontal_gradient = (BLUELANDS_BIOME_HEIGHT-base_height)/CYANLANDS_HORIZONTAL_UPPERGRADIENT_LENGTH
						end
					end
				end
				if( base_height > BLUELANDS_BIOME_HEIGHT ) then
					height = height + CYANLANDS_LAYER_HEIGHT+BLUELANDS_DISPLACEMENT
					max_height = height + BLUELANDS_LAYER_HEIGHT
					levistone_id = cid_stone_blue
					grass_id = cid_grass_blue
					dirt_id = cid_dirt_blue
					plant1_id = cid_grass1_blue
					bottom_gradient_start = height
					bottom_gradient_length = BLUELANDS_BOTTOMGRADIENT_LENGHT
					bottom_gradient_end = bottom_gradient_start + bottom_gradient_length
					top_gradient_start = height + BLUELANDS_TOPGRADIENT_START
					top_gradient_length = BLUELANDS_TOPGRADIENT_LENGTH
					horizontal_gradient_start = BLUELANDS_BIOME_HEIGHT+BLUELANDS_HORIZONTAL_LOWERGRADIENT_LENGTH
					horizontal_gradient = 1
					if( base_height < horizontal_gradient_start ) then
						horizontal_gradient = (base_height-BLUELANDS_BIOME_HEIGHT)/BLUELANDS_HORIZONTAL_LOWERGRADIENT_LENGTH
					end
				end
				if( (y > height) and (y < max_height) ) then
					local node_index_in_mapchunk = generate_VoxelArea:index(x,y,z)
					local cyanland1_density = noise_buffers.cyanland1_buffer[node_index_in_mapchunk]
					local cyanland2_density = noise_buffers.cyanland2_buffer[node_index_in_mapchunk]
					local density = (cyanland1_density + cyanland2_density)/2
					local top_gradient = 1
					local bottom_gradient = 1
					local gradient = horizontal_gradient
					if( y>top_gradient_start ) then
						top_gradient = (max_height-y)/top_gradient_length
					end
					if( y<bottom_gradient_end ) then
						bottom_gradient = (y-bottom_gradient_start)/bottom_gradient_length
					end
					-- pick the smaller gradient. assumes bottom and top gradient never overlaps
					if( top_gradient < horizontal_gradient ) then gradient = top_gradient end
					if( bottom_gradient < horizontal_gradient ) then gradient = bottom_gradient end
					
					density = ( (density+1)*gradient ) -1
					if( density > STONE_THRESHOLD) then
						node_index_in_emerged = buffer_VoxelArea:index(x, y, z)
						cid_buffer[node_index_in_emerged] = levistone_id 
					elseif( (y > ymin)
					and ( density > DIRT_THRESHOLD )
					--TODO by checking not air, this allows to dirt to balance above grass
					and (cid_buffer[buffer_VoxelArea:index(x, y-1, z)]~=cid_air) ) then --this seems to cause performance issues... maybe accesing array in wrong order takes it away from cpu cache?
					-- skiping checks, but still writing to array  makes manip time twice as fast
					--could be the checks themselves or wrong read order
						node_index_in_emerged = buffer_VoxelArea:index(x, y, z)
						cid_buffer[node_index_in_emerged] = dirt_id
					elseif(    ( y > ymin)
					and ( (cid_buffer[buffer_VoxelArea:index(x, y-1, z)]==dirt_id )
						or (cid_buffer[buffer_VoxelArea:index(x, y-1, z)]==levistone_id) )   ) then
						node_index_in_emerged = buffer_VoxelArea:index(x, y, z)
						cid_buffer[node_index_in_emerged] = grass_id
					elseif( (y > ymin)
					and ( cid_buffer[buffer_VoxelArea:index(x, y-1, z)]==grass_id )
					and ( math.random() < GRASS_FREQUENCY ) ) then
						if( math.fmod(y, 25) == 0) and (math.fmod(x, 25) == 0 ) then
							--what is this?
						end
						node_index_in_emerged = buffer_VoxelArea:index(x, y, z)
						cid_buffer[node_index_in_emerged] = plant1_id
						local p2 = 8
						local r = math.random()
						if( r > 0.7 ) then p2 = 10 elseif( r < 0.3 ) then p2 = 40 end
						param2_buffer[node_index_in_emerged] = p2
					end
					
				end
				if( DEBUG_FLAGS.debug_blocks ) then
					if (( y == math.floor(height) ) or ( y == math.floor(max_height) ))
					then
						node_index_in_emerged = buffer_VoxelArea:index(x, y, z)
						cid_buffer[node_index_in_emerged] = cid_debugnode_edge
					end
					local actualedge_min = height
					local actualedge_max = top_gradient_start+top_gradient_length*0.5 -- 0.5 is dirt density remaped to [0:1]
					if (( y == math.floor(actualedge_min) ) or ( y == math.floor(actualedge_max) ))
					then
						node_index_in_emerged = buffer_VoxelArea:index(x, y, z)
						cid_buffer[node_index_in_emerged] = cid_debugnode_actualedge
					end
					if (( x == xmin+16*2+8) or ( x == xmin+16*2+1+8 ))
					and(( y == ymin+16*2+8) or ( y == ymin+16*2+1+8 ))
					and(( z == zmin+16*2+8) or ( z == zmin+16*2+1+8 ))
					then
						node_index_in_emerged = buffer_VoxelArea:index(x, y, z)
						cid_buffer[node_index_in_emerged] = cid_debugnode_mapchunk
					end
				end
			end
			node_2d_index = node_2d_index - generate_VoxelArea:getExtent().x
		end
		node_2d_index = node_2d_index + generate_VoxelArea:getExtent().x 
	end
	time_elapsed = ( os.clock()-time_init )*1000
	debug_data.timeAccumulator_genIDs = debug_data.timeAccumulator_genIDs + time_elapsed

end

local debug_timer = 0

_ether_lands.on_globalstep = function( delta_time )
	debug_timer = debug_timer + delta_time
	if( debug_timer >= DEBUG_FREQUENCY ) then
			minetest.log("warning", "----timers-------------------------------------------------------------" )
			minetest.log("warning", "avg total time: "..math.ceil( debug_data.timeAccumulator_total/debug_data.mapblock_count).."ms" )
			minetest.log("warning", "avg get noise values: "..math.ceil( debug_data.timeAccumulator_getNoise/debug_data.mapblock_count).."ms" )
			minetest.log("warning", "avg manipulate mapchunk data: "..math.ceil( debug_data.timeAccumulator_genIDs/debug_data.mapblock_count).."ms" )
			minetest.log("warning", "avg write to vm: "..math.ceil( debug_data.timeAccumulator_writeVM/debug_data.mapblock_count).."ms" )
			minetest.log("warning", "avg write to map: "..math.ceil(debug_data.timeAccumulator_writeMap/debug_data.mapblock_count).."ms" )
			minetest.log("warning", "avg place schematics: "..math.ceil(debug_data.timeAccumulator_placeSchematics/debug_data.mapblock_count).."ms" )
			minetest.log( "warning", "memory: " .. math.ceil(collectgarbage("count")/1024) )
			minetest.log("warning", "-----------------------------------------------------------------------" )
			debug_timer = 0
	end
end

_ether_lands.on_generated = function( min_mapchunk_position, max_mapchunk_position, seed )
	if( min_mapchunk_position.y > REALM_FINISH ) then return end
	if( max_mapchunk_position.y < REALM_START ) then return end
	
	debug_data.mapblock_count = debug_data.mapblock_count + 1
	local time_total_init = os.clock()
	local time_total_elapsed

	--this is done here because afaik there is no way to get mapchunk dimensions elsewhere
	-- try to get smaller voxelmanips since we dont care whatever coremapgen is doing
	local voxelmanip, min_emerged_position, max_emerged_position = minetest.get_mapgen_object("voxelmanip")
	min_mapchunk_position.y = min_mapchunk_position.y - 8 --generate area that is bigger in -y direction to avoid discontinuities due to dirt requiring stone support
	
	local mapchunk_area = VoxelArea:new{MinEdge=min_mapchunk_position, MaxEdge=max_mapchunk_position}
	local emerged_area = VoxelArea:new{MinEdge=min_emerged_position, MaxEdge=max_emerged_position}
	voxelmanip:get_data(cid_data)
	voxelmanip:get_param2_data(param2_data)

	ether_lands.generate_area( mapchunk_area, emerged_area, cid_data, param2_data,
		{ heightmap_buffer=heightmap_values, cyanland1_buffer=cyanland1_values, cyanland2_buffer=cyanland2_values }, voxelmanip )
	local time_writeToVM_init = os.clock()
	local time_writeToVM_elapsed
	
	voxelmanip:set_data(cid_data)
	voxelmanip:set_param2_data(param2_data)
	voxelmanip:set_lighting( {day=9, night=0} )
	voxelmanip:calc_lighting()

	time_writeToVM_elapsed = (os.clock()-time_writeToVM_init)*1000
	debug_data.timeAccumulator_writeVM = debug_data.timeAccumulator_writeVM + time_writeToVM_elapsed

	local time_writeToMap_init = os.clock()
	local time_writeToMap_elapsed
	
	voxelmanip:write_to_map()

	
	time_writeToMap_elapsed = (os.clock()-time_writeToMap_init)*1000
	debug_data.timeAccumulator_writeMap = debug_data.timeAccumulator_writeMap + time_writeToMap_elapsed
	
	local time_placeSchematics_init = os.clock()
	local time_placeSchematics_elapsed
	
	_ether_lands.generate_structures( cid_data, mapchunk_area, emerged_area )
	
	time_placeSchematics_elapsed = (os.clock()-time_placeSchematics_init)*1000
	debug_data.timeAccumulator_placeSchematics = debug_data.timeAccumulator_placeSchematics + time_placeSchematics_elapsed
	
	time_total_elapsed = (os.clock()-time_total_init)*1000
	debug_data.timeAccumulator_total = debug_data.timeAccumulator_total + time_total_elapsed

end

local function find_spawn()
	for gx = -20000, 20000, 1000 do
	for gz = -20000, 20000, 1000 do
		minetest.log("warning", "trying to find spawn at x: ".. gx .. " z: " ..gz)
		local ymax = REALM_FINISH
		local ymin = REALM_START 
		local gen_area = VoxelArea:new{ MinEdge={ x=gx, y=ymin, z=gz }, MaxEdge={ x=gx+1, y=ymax, z=gz+1} } -- it seems the perling generators cant handle a 1x1 column TODO report issue on this
		local cid_buffer = {}
		cid_buffer[gen_area:getVolume()] = 0
		for i = 1, gen_area:getVolume() do
			cid_buffer[i] = cid_air
		end
		local param2_buffer = {}
		param2_buffer[gen_area:getVolume()] = 0
		local h_values = {}
		h_values[4] = 0
		local c1_values = {}
		c1_values[gen_area:getVolume()] = 0
		local c2_values = {}
		c2_values[gen_area:getVolume()] = 0
		ether_lands.generate_area( gen_area, gen_area, cid_buffer, param2_buffer,
			{ heightmap_buffer=h_values, cyanland1_buffer=c1_values, cyanland2_buffer=c2_values } )
		local current_biome
		local skip=false
		local height = (h_values[1]+1)/2
		height = height*HEIGHT_VARIANCE
		if( DEBUG_FLAGS.cherrypick_biome ) then
				if( height < BLUELANDS_BIOME_HEIGHT ) then current_biome = "cyanlands" end
				if( height < ORANGELANDS_BIOME_HEIGHT ) then current_biome = "orangelands" end
				if( height > BLUELANDS_BIOME_HEIGHT ) then current_biome = "bluelands" end
				if( current_biome~=DESIRED_BIOME ) then skip=true  end
		end
		if not skip then
		for y = ymax-1, ymin, -1 do
			local i = gen_area:index( gx, y, gz )
			local iabove = gen_area:index( gx, y+1, gz)
			local ibelow = gen_area:index( gx, y-1, gz)
			-- yikes
			if( ( (cid_buffer[i] == cid_air)
				or (cid_buffer[i] == cid_grass1_cyan)
				or (cid_buffer[i] == cid_grass1_orange)
				or (cid_buffer[i] == cid_grass1_blue) )
			and( cid_buffer[iabove] == cid_air)
			and( ( cid_buffer[ibelow] == cid_grass_cyan )
				or (cid_buffer[ibelow]==cid_grass_orange)
				or (cid_buffer[ibelow]==cid_grass_blue) ) 	)
			then
				return {x=gx, y=y, z=gz}
			end
		end
		end
	end
	end
	return {x=0,y=0,z=0}
end

minetest.register_on_joinplayer( function( player )
	minetest.set_timeofday(8/24)
	if( DEBUG_FLAGS.cherrypicked_spawn ) then player:set_pos({x=510,y=656, z=4850})
	else
		player:set_pos(find_spawn())
	end
	player:set_sky( {a=1,r=240,g=240,b=230}, "plain", false )
	player:set_physics_override( { speed=2} )
	if( DEBUG_FLAGS.give_privs ) then
		minetest.set_player_privs("singleplayer", {fly=true,noclip=true,fast=true, settime=true})
	end
end )

minetest.register_globalstep( _ether_lands.on_globalstep )
minetest.register_on_generated( _ether_lands.on_generated )
